<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>py3dtilers.Common.group API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>py3dtilers.Common.group</code></h1>
</header>
<section id="section-intro">
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">import os
from os import listdir
import json
from shapely.geometry import Point, Polygon
from ..Common import FeatureList
from ..Common import kd_tree


class Group():
    &#34;&#34;&#34;
    Contains an instance of FeatureList
    It can also contain additional polygon points (used to create LOA nodes)
    &#34;&#34;&#34;

    def __init__(self, feature_list: FeatureList, polygons=list()):
        self.feature_list = feature_list
        self.polygons = polygons

    def get_centroid(self):
        &#34;&#34;&#34;
        Get the centroid of the group.
        :return: a 3D point ([x, y, z])
        &#34;&#34;&#34;
        return self.feature_list.get_centroid()

    def round_coordinates(self, coordinates, base):
        &#34;&#34;&#34;
        Round the coordinates to the closer multiple of a base.
        :param coordinates: a 3D point ([x, y, z])
        :param int base: the base used to round the coordinates

        :return: a 3D point rounded to the closer multiples of the base
        &#34;&#34;&#34;
        rounded_coord = coordinates
        for i in range(0, len(coordinates)):
            rounded_coord[i] = base * round(coordinates[i] / base)
        return rounded_coord

    def add_materials(self, materials):
        &#34;&#34;&#34;
        Keep only the materials used by the features of this group,
        among all the materials created, and add them to the features.
        :param materials: an array of all the materials
        &#34;&#34;&#34;
        seen_mat_indexes = dict()
        group_materials = []
        for feature in self.feature_list:
            mat_index = feature.material_index
            if mat_index not in seen_mat_indexes:
                seen_mat_indexes[mat_index] = len(group_materials)
                group_materials.append(materials[mat_index])
            feature.material_index = seen_mat_indexes[mat_index]
        self.feature_list.set_materials(group_materials)


class Groups():
    &#34;&#34;&#34;
    Contains a list of Group
    &#34;&#34;&#34;

    # Used to put in a same group the features which are in a same 1000 m^3 cube.
    DEFAULT_CUBE_SIZE = 1000

    def __init__(self, feature_list: FeatureList, polygons_path=None, kd_tree_max=500):
        &#34;&#34;&#34;
        Distribute the features contained in feature_list into different Group
        The way to distribute the features depends on the parameters
        :param feature_list: an instance of FeatureList containing features to distribute into Group
        :param polygons_path: the path to a folder containing polygons as .geojson files.
        When this param is not None, it means we want to group features by polygons
        :param kd_tree_max: the maximum number of features in each list created by the kd_tree
        &#34;&#34;&#34;
        if ((type(feature_list) is list)):
            self.group_array_of_feature_list(feature_list)
        else:
            self.materials = feature_list.materials
            if polygons_path is not None:
                self.group_objects_by_polygons(feature_list, polygons_path)
            else:
                self.group_objects_with_kdtree(feature_list, kd_tree_max)
            self.set_materials(self.materials)

    def get_groups_as_list(self):
        &#34;&#34;&#34;
        Return the groups as a list.
        :return: the groups as list
        &#34;&#34;&#34;
        return self.groups

    def set_materials(self, materials):
        &#34;&#34;&#34;
        Set the materials of each group.
        :param materials: an array of all the materials
        &#34;&#34;&#34;
        for group in self.groups:
            group.add_materials(materials)

    def group_array_of_feature_list(self, feature_lists_array: list[FeatureList]):
        self.groups = list()
        for feature_list in feature_lists_array:
            group = Group(feature_list)
            self.groups.append(group)

    def group_objects_with_kdtree(self, feature_list: FeatureList, kd_tree_max=500):
        &#34;&#34;&#34;
        Create groups of features. The features are distributed into FeatureList of (by default) max 500 features.
        The distribution depends on the centroid of each feature.
        :param feature_list: a FeatureList
        :param kd_tree_max: the maximum number of features in each FeatureList
        &#34;&#34;&#34;
        groups = list()
        objects = kd_tree(feature_list, kd_tree_max)
        for feature_list in objects:
            group = Group(feature_list)
            groups.append(group)
        self.groups = groups

    def group_objects_by_polygons(self, feature_list: FeatureList, polygons_path):
        &#34;&#34;&#34;
        Load the polygons from GeoJSON files.
        Group the features depending in which polygon they are contained.
        :param feature_list: all the features
        :param polygons_path: the path to the file(s) containing polygons
        &#34;&#34;&#34;
        polygons = list()
        files = []

        if(os.path.isdir(polygons_path)):
            geojson_dir = listdir(polygons_path)
            for geojson_file in geojson_dir:
                file_path = os.path.join(polygons_path, geojson_file)
                if(os.path.isfile(file_path)):
                    files.append(file_path)
        else:
            files.append(polygons_path)

        # Read all the polygons in the file(s)
        for file in files:
            if(&#34;.geojson&#34; in file or &#34;.json&#34; in file):
                with open(file) as f:
                    gjContent = json.load(f)
                for feature in gjContent[&#39;features&#39;]:
                    if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;Polygon&#39;:
                        coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][:-1]
                    if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;MultiPolygon&#39;:
                        coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][0][:-1]
                    polygons.append(Polygon(coords))
        self.groups = self.distribute_objects_in_polygons(feature_list, polygons)

    def distribute_objects_in_polygons(self, feature_list: FeatureList, polygons):
        &#34;&#34;&#34;
        Distribute the features in the polygons.
        The features in the same polygon are grouped together. The Group created will also contain the points of the polygon.
        If a feature is not in any polygon, create a Group containing only this feature. This group won&#39;t have addtional points.
        :param polygons: a list of Shapely polygons
        &#34;&#34;&#34;

        features_dict = {}
        features_without_poly = list()

        # For each feature, find the polygon containing it
        for i, feature in enumerate(feature_list):
            p = Point(feature.get_centroid())
            in_polygon = False
            for index, polygon in enumerate(polygons):
                if p.within(polygon):
                    if index not in features_dict:
                        features_dict[index] = []
                    features_dict[index].append(i)
                    in_polygon = True
                    break
            if not in_polygon:
                features_without_poly.append(i)

        # Create a list of Group
        groups = list()

        for key in features_dict:
            polygon = polygons[key].exterior.coords[:-1]
            contained_features = FeatureList([feature_list[i] for i in features_dict[key]])
            group = Group(contained_features, polygons=[polygon])
            groups.append(group)

        for feature_index in features_without_poly:
            group = Group(FeatureList([feature_list[feature_index]]))
            groups.append(group)

        return self.distribute_groups_in_cubes(groups, Groups.DEFAULT_CUBE_SIZE)

    def distribute_groups_in_cubes(self, groups: list[Group], cube_size):
        &#34;&#34;&#34;
        Merges together the groups in order to reduce the number of tiles.
        The groups are distributed into cubes of a grid. The groups in the same cube are merged together.
        :param groups: the groups to distribute into cubes
        :param cube_size: the size of the cubes

        :return: merged groups
        &#34;&#34;&#34;
        groups_dict = {}

        # Create a dictionary key: cubes center (x,y,z), with geometry (boolean); value: list of groups index
        for i in range(0, len(groups)):
            closest_cube = groups[i].round_coordinates(groups[i].get_centroid(), cube_size)
            if tuple(closest_cube) in groups_dict:
                groups_dict[tuple(closest_cube)].append(i)
            else:
                groups_dict[tuple(closest_cube)] = [i]

        # Merge the groups in the same cube and create new groups
        groups_in_cube = list()
        for cube in groups_dict:
            groups_in_cube.append(self.merge_groups_together(groups, groups_dict[cube]))
        return groups_in_cube

    def merge_groups_together(self, groups: list[Group], group_indexes):
        &#34;&#34;&#34;
        Creates a Group from a list of Groups
        :param groups: all the groups
        :param group_indexes: the indexes of the groups to merge together

        :return: a new group containing the features of all the groups
        &#34;&#34;&#34;
        features = list()
        polygons = list()
        for index in group_indexes:
            features.extend(groups[index].feature_list)
            polygons.extend(groups[index].polygons)
        return Group(FeatureList(features), polygons=polygons)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="py3dtilers.Common.group.Group"><code class="flex name class">
<span>class <span class="ident">Group</span></span>
<span>(</span><span>feature_list: <a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>, polygons=[])</span>
</code></dt>
<dd>
<div class="desc"><p>Contains an instance of FeatureList
It can also contain additional polygon points (used to create LOA nodes)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Group():
    &#34;&#34;&#34;
    Contains an instance of FeatureList
    It can also contain additional polygon points (used to create LOA nodes)
    &#34;&#34;&#34;

    def __init__(self, feature_list: FeatureList, polygons=list()):
        self.feature_list = feature_list
        self.polygons = polygons

    def get_centroid(self):
        &#34;&#34;&#34;
        Get the centroid of the group.
        :return: a 3D point ([x, y, z])
        &#34;&#34;&#34;
        return self.feature_list.get_centroid()

    def round_coordinates(self, coordinates, base):
        &#34;&#34;&#34;
        Round the coordinates to the closer multiple of a base.
        :param coordinates: a 3D point ([x, y, z])
        :param int base: the base used to round the coordinates

        :return: a 3D point rounded to the closer multiples of the base
        &#34;&#34;&#34;
        rounded_coord = coordinates
        for i in range(0, len(coordinates)):
            rounded_coord[i] = base * round(coordinates[i] / base)
        return rounded_coord

    def add_materials(self, materials):
        &#34;&#34;&#34;
        Keep only the materials used by the features of this group,
        among all the materials created, and add them to the features.
        :param materials: an array of all the materials
        &#34;&#34;&#34;
        seen_mat_indexes = dict()
        group_materials = []
        for feature in self.feature_list:
            mat_index = feature.material_index
            if mat_index not in seen_mat_indexes:
                seen_mat_indexes[mat_index] = len(group_materials)
                group_materials.append(materials[mat_index])
            feature.material_index = seen_mat_indexes[mat_index]
        self.feature_list.set_materials(group_materials)</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="py3dtilers.Common.group.Group.add_materials"><code class="name flex">
<span>def <span class="ident">add_materials</span></span>(<span>self, materials)</span>
</code></dt>
<dd>
<div class="desc"><p>Keep only the materials used by the features of this group,
among all the materials created, and add them to the features.
:param materials: an array of all the materials</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_materials(self, materials):
    &#34;&#34;&#34;
    Keep only the materials used by the features of this group,
    among all the materials created, and add them to the features.
    :param materials: an array of all the materials
    &#34;&#34;&#34;
    seen_mat_indexes = dict()
    group_materials = []
    for feature in self.feature_list:
        mat_index = feature.material_index
        if mat_index not in seen_mat_indexes:
            seen_mat_indexes[mat_index] = len(group_materials)
            group_materials.append(materials[mat_index])
        feature.material_index = seen_mat_indexes[mat_index]
    self.feature_list.set_materials(group_materials)</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Group.get_centroid"><code class="name flex">
<span>def <span class="ident">get_centroid</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Get the centroid of the group.
:return: a 3D point ([x, y, z])</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_centroid(self):
    &#34;&#34;&#34;
    Get the centroid of the group.
    :return: a 3D point ([x, y, z])
    &#34;&#34;&#34;
    return self.feature_list.get_centroid()</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Group.round_coordinates"><code class="name flex">
<span>def <span class="ident">round_coordinates</span></span>(<span>self, coordinates, base)</span>
</code></dt>
<dd>
<div class="desc"><p>Round the coordinates to the closer multiple of a base.
:param coordinates: a 3D point ([x, y, z])
:param int base: the base used to round the coordinates</p>
<p>:return: a 3D point rounded to the closer multiples of the base</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def round_coordinates(self, coordinates, base):
    &#34;&#34;&#34;
    Round the coordinates to the closer multiple of a base.
    :param coordinates: a 3D point ([x, y, z])
    :param int base: the base used to round the coordinates

    :return: a 3D point rounded to the closer multiples of the base
    &#34;&#34;&#34;
    rounded_coord = coordinates
    for i in range(0, len(coordinates)):
        rounded_coord[i] = base * round(coordinates[i] / base)
    return rounded_coord</code></pre>
</details>
</dd>
</dl>
</dd>
<dt id="py3dtilers.Common.group.Groups"><code class="flex name class">
<span>class <span class="ident">Groups</span></span>
<span>(</span><span>feature_list: <a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>, polygons_path=None, kd_tree_max=500)</span>
</code></dt>
<dd>
<div class="desc"><p>Contains a list of Group</p>
<p>Distribute the features contained in feature_list into different Group
The way to distribute the features depends on the parameters
:param feature_list: an instance of FeatureList containing features to distribute into Group
:param polygons_path: the path to a folder containing polygons as .geojson files.
When this param is not None, it means we want to group features by polygons
:param kd_tree_max: the maximum number of features in each list created by the kd_tree</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Groups():
    &#34;&#34;&#34;
    Contains a list of Group
    &#34;&#34;&#34;

    # Used to put in a same group the features which are in a same 1000 m^3 cube.
    DEFAULT_CUBE_SIZE = 1000

    def __init__(self, feature_list: FeatureList, polygons_path=None, kd_tree_max=500):
        &#34;&#34;&#34;
        Distribute the features contained in feature_list into different Group
        The way to distribute the features depends on the parameters
        :param feature_list: an instance of FeatureList containing features to distribute into Group
        :param polygons_path: the path to a folder containing polygons as .geojson files.
        When this param is not None, it means we want to group features by polygons
        :param kd_tree_max: the maximum number of features in each list created by the kd_tree
        &#34;&#34;&#34;
        if ((type(feature_list) is list)):
            self.group_array_of_feature_list(feature_list)
        else:
            self.materials = feature_list.materials
            if polygons_path is not None:
                self.group_objects_by_polygons(feature_list, polygons_path)
            else:
                self.group_objects_with_kdtree(feature_list, kd_tree_max)
            self.set_materials(self.materials)

    def get_groups_as_list(self):
        &#34;&#34;&#34;
        Return the groups as a list.
        :return: the groups as list
        &#34;&#34;&#34;
        return self.groups

    def set_materials(self, materials):
        &#34;&#34;&#34;
        Set the materials of each group.
        :param materials: an array of all the materials
        &#34;&#34;&#34;
        for group in self.groups:
            group.add_materials(materials)

    def group_array_of_feature_list(self, feature_lists_array: list[FeatureList]):
        self.groups = list()
        for feature_list in feature_lists_array:
            group = Group(feature_list)
            self.groups.append(group)

    def group_objects_with_kdtree(self, feature_list: FeatureList, kd_tree_max=500):
        &#34;&#34;&#34;
        Create groups of features. The features are distributed into FeatureList of (by default) max 500 features.
        The distribution depends on the centroid of each feature.
        :param feature_list: a FeatureList
        :param kd_tree_max: the maximum number of features in each FeatureList
        &#34;&#34;&#34;
        groups = list()
        objects = kd_tree(feature_list, kd_tree_max)
        for feature_list in objects:
            group = Group(feature_list)
            groups.append(group)
        self.groups = groups

    def group_objects_by_polygons(self, feature_list: FeatureList, polygons_path):
        &#34;&#34;&#34;
        Load the polygons from GeoJSON files.
        Group the features depending in which polygon they are contained.
        :param feature_list: all the features
        :param polygons_path: the path to the file(s) containing polygons
        &#34;&#34;&#34;
        polygons = list()
        files = []

        if(os.path.isdir(polygons_path)):
            geojson_dir = listdir(polygons_path)
            for geojson_file in geojson_dir:
                file_path = os.path.join(polygons_path, geojson_file)
                if(os.path.isfile(file_path)):
                    files.append(file_path)
        else:
            files.append(polygons_path)

        # Read all the polygons in the file(s)
        for file in files:
            if(&#34;.geojson&#34; in file or &#34;.json&#34; in file):
                with open(file) as f:
                    gjContent = json.load(f)
                for feature in gjContent[&#39;features&#39;]:
                    if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;Polygon&#39;:
                        coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][:-1]
                    if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;MultiPolygon&#39;:
                        coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][0][:-1]
                    polygons.append(Polygon(coords))
        self.groups = self.distribute_objects_in_polygons(feature_list, polygons)

    def distribute_objects_in_polygons(self, feature_list: FeatureList, polygons):
        &#34;&#34;&#34;
        Distribute the features in the polygons.
        The features in the same polygon are grouped together. The Group created will also contain the points of the polygon.
        If a feature is not in any polygon, create a Group containing only this feature. This group won&#39;t have addtional points.
        :param polygons: a list of Shapely polygons
        &#34;&#34;&#34;

        features_dict = {}
        features_without_poly = list()

        # For each feature, find the polygon containing it
        for i, feature in enumerate(feature_list):
            p = Point(feature.get_centroid())
            in_polygon = False
            for index, polygon in enumerate(polygons):
                if p.within(polygon):
                    if index not in features_dict:
                        features_dict[index] = []
                    features_dict[index].append(i)
                    in_polygon = True
                    break
            if not in_polygon:
                features_without_poly.append(i)

        # Create a list of Group
        groups = list()

        for key in features_dict:
            polygon = polygons[key].exterior.coords[:-1]
            contained_features = FeatureList([feature_list[i] for i in features_dict[key]])
            group = Group(contained_features, polygons=[polygon])
            groups.append(group)

        for feature_index in features_without_poly:
            group = Group(FeatureList([feature_list[feature_index]]))
            groups.append(group)

        return self.distribute_groups_in_cubes(groups, Groups.DEFAULT_CUBE_SIZE)

    def distribute_groups_in_cubes(self, groups: list[Group], cube_size):
        &#34;&#34;&#34;
        Merges together the groups in order to reduce the number of tiles.
        The groups are distributed into cubes of a grid. The groups in the same cube are merged together.
        :param groups: the groups to distribute into cubes
        :param cube_size: the size of the cubes

        :return: merged groups
        &#34;&#34;&#34;
        groups_dict = {}

        # Create a dictionary key: cubes center (x,y,z), with geometry (boolean); value: list of groups index
        for i in range(0, len(groups)):
            closest_cube = groups[i].round_coordinates(groups[i].get_centroid(), cube_size)
            if tuple(closest_cube) in groups_dict:
                groups_dict[tuple(closest_cube)].append(i)
            else:
                groups_dict[tuple(closest_cube)] = [i]

        # Merge the groups in the same cube and create new groups
        groups_in_cube = list()
        for cube in groups_dict:
            groups_in_cube.append(self.merge_groups_together(groups, groups_dict[cube]))
        return groups_in_cube

    def merge_groups_together(self, groups: list[Group], group_indexes):
        &#34;&#34;&#34;
        Creates a Group from a list of Groups
        :param groups: all the groups
        :param group_indexes: the indexes of the groups to merge together

        :return: a new group containing the features of all the groups
        &#34;&#34;&#34;
        features = list()
        polygons = list()
        for index in group_indexes:
            features.extend(groups[index].feature_list)
            polygons.extend(groups[index].polygons)
        return Group(FeatureList(features), polygons=polygons)</code></pre>
</details>
<h3>Class variables</h3>
<dl>
<dt id="py3dtilers.Common.group.Groups.DEFAULT_CUBE_SIZE"><code class="name">var <span class="ident">DEFAULT_CUBE_SIZE</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="py3dtilers.Common.group.Groups.distribute_groups_in_cubes"><code class="name flex">
<span>def <span class="ident">distribute_groups_in_cubes</span></span>(<span>self, groups: list[<a title="py3dtilers.Common.group.Group" href="#py3dtilers.Common.group.Group">Group</a>], cube_size)</span>
</code></dt>
<dd>
<div class="desc"><p>Merges together the groups in order to reduce the number of tiles.
The groups are distributed into cubes of a grid. The groups in the same cube are merged together.
:param groups: the groups to distribute into cubes
:param cube_size: the size of the cubes</p>
<p>:return: merged groups</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def distribute_groups_in_cubes(self, groups: list[Group], cube_size):
    &#34;&#34;&#34;
    Merges together the groups in order to reduce the number of tiles.
    The groups are distributed into cubes of a grid. The groups in the same cube are merged together.
    :param groups: the groups to distribute into cubes
    :param cube_size: the size of the cubes

    :return: merged groups
    &#34;&#34;&#34;
    groups_dict = {}

    # Create a dictionary key: cubes center (x,y,z), with geometry (boolean); value: list of groups index
    for i in range(0, len(groups)):
        closest_cube = groups[i].round_coordinates(groups[i].get_centroid(), cube_size)
        if tuple(closest_cube) in groups_dict:
            groups_dict[tuple(closest_cube)].append(i)
        else:
            groups_dict[tuple(closest_cube)] = [i]

    # Merge the groups in the same cube and create new groups
    groups_in_cube = list()
    for cube in groups_dict:
        groups_in_cube.append(self.merge_groups_together(groups, groups_dict[cube]))
    return groups_in_cube</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.distribute_objects_in_polygons"><code class="name flex">
<span>def <span class="ident">distribute_objects_in_polygons</span></span>(<span>self, feature_list: <a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>, polygons)</span>
</code></dt>
<dd>
<div class="desc"><p>Distribute the features in the polygons.
The features in the same polygon are grouped together. The Group created will also contain the points of the polygon.
If a feature is not in any polygon, create a Group containing only this feature. This group won't have addtional points.
:param polygons: a list of Shapely polygons</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def distribute_objects_in_polygons(self, feature_list: FeatureList, polygons):
    &#34;&#34;&#34;
    Distribute the features in the polygons.
    The features in the same polygon are grouped together. The Group created will also contain the points of the polygon.
    If a feature is not in any polygon, create a Group containing only this feature. This group won&#39;t have addtional points.
    :param polygons: a list of Shapely polygons
    &#34;&#34;&#34;

    features_dict = {}
    features_without_poly = list()

    # For each feature, find the polygon containing it
    for i, feature in enumerate(feature_list):
        p = Point(feature.get_centroid())
        in_polygon = False
        for index, polygon in enumerate(polygons):
            if p.within(polygon):
                if index not in features_dict:
                    features_dict[index] = []
                features_dict[index].append(i)
                in_polygon = True
                break
        if not in_polygon:
            features_without_poly.append(i)

    # Create a list of Group
    groups = list()

    for key in features_dict:
        polygon = polygons[key].exterior.coords[:-1]
        contained_features = FeatureList([feature_list[i] for i in features_dict[key]])
        group = Group(contained_features, polygons=[polygon])
        groups.append(group)

    for feature_index in features_without_poly:
        group = Group(FeatureList([feature_list[feature_index]]))
        groups.append(group)

    return self.distribute_groups_in_cubes(groups, Groups.DEFAULT_CUBE_SIZE)</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.get_groups_as_list"><code class="name flex">
<span>def <span class="ident">get_groups_as_list</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Return the groups as a list.
:return: the groups as list</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_groups_as_list(self):
    &#34;&#34;&#34;
    Return the groups as a list.
    :return: the groups as list
    &#34;&#34;&#34;
    return self.groups</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.group_array_of_feature_list"><code class="name flex">
<span>def <span class="ident">group_array_of_feature_list</span></span>(<span>self, feature_lists_array: list[<a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>])</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def group_array_of_feature_list(self, feature_lists_array: list[FeatureList]):
    self.groups = list()
    for feature_list in feature_lists_array:
        group = Group(feature_list)
        self.groups.append(group)</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.group_objects_by_polygons"><code class="name flex">
<span>def <span class="ident">group_objects_by_polygons</span></span>(<span>self, feature_list: <a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>, polygons_path)</span>
</code></dt>
<dd>
<div class="desc"><p>Load the polygons from GeoJSON files.
Group the features depending in which polygon they are contained.
:param feature_list: all the features
:param polygons_path: the path to the file(s) containing polygons</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def group_objects_by_polygons(self, feature_list: FeatureList, polygons_path):
    &#34;&#34;&#34;
    Load the polygons from GeoJSON files.
    Group the features depending in which polygon they are contained.
    :param feature_list: all the features
    :param polygons_path: the path to the file(s) containing polygons
    &#34;&#34;&#34;
    polygons = list()
    files = []

    if(os.path.isdir(polygons_path)):
        geojson_dir = listdir(polygons_path)
        for geojson_file in geojson_dir:
            file_path = os.path.join(polygons_path, geojson_file)
            if(os.path.isfile(file_path)):
                files.append(file_path)
    else:
        files.append(polygons_path)

    # Read all the polygons in the file(s)
    for file in files:
        if(&#34;.geojson&#34; in file or &#34;.json&#34; in file):
            with open(file) as f:
                gjContent = json.load(f)
            for feature in gjContent[&#39;features&#39;]:
                if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;Polygon&#39;:
                    coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][:-1]
                if feature[&#39;geometry&#39;][&#39;type&#39;] == &#39;MultiPolygon&#39;:
                    coords = feature[&#39;geometry&#39;][&#39;coordinates&#39;][0][0][:-1]
                polygons.append(Polygon(coords))
    self.groups = self.distribute_objects_in_polygons(feature_list, polygons)</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.group_objects_with_kdtree"><code class="name flex">
<span>def <span class="ident">group_objects_with_kdtree</span></span>(<span>self, feature_list: <a title="py3dtilers.Common.feature.FeatureList" href="feature.html#py3dtilers.Common.feature.FeatureList">FeatureList</a>, kd_tree_max=500)</span>
</code></dt>
<dd>
<div class="desc"><p>Create groups of features. The features are distributed into FeatureList of (by default) max 500 features.
The distribution depends on the centroid of each feature.
:param feature_list: a FeatureList
:param kd_tree_max: the maximum number of features in each FeatureList</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def group_objects_with_kdtree(self, feature_list: FeatureList, kd_tree_max=500):
    &#34;&#34;&#34;
    Create groups of features. The features are distributed into FeatureList of (by default) max 500 features.
    The distribution depends on the centroid of each feature.
    :param feature_list: a FeatureList
    :param kd_tree_max: the maximum number of features in each FeatureList
    &#34;&#34;&#34;
    groups = list()
    objects = kd_tree(feature_list, kd_tree_max)
    for feature_list in objects:
        group = Group(feature_list)
        groups.append(group)
    self.groups = groups</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.merge_groups_together"><code class="name flex">
<span>def <span class="ident">merge_groups_together</span></span>(<span>self, groups: list[<a title="py3dtilers.Common.group.Group" href="#py3dtilers.Common.group.Group">Group</a>], group_indexes)</span>
</code></dt>
<dd>
<div class="desc"><p>Creates a Group from a list of Groups
:param groups: all the groups
:param group_indexes: the indexes of the groups to merge together</p>
<p>:return: a new group containing the features of all the groups</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def merge_groups_together(self, groups: list[Group], group_indexes):
    &#34;&#34;&#34;
    Creates a Group from a list of Groups
    :param groups: all the groups
    :param group_indexes: the indexes of the groups to merge together

    :return: a new group containing the features of all the groups
    &#34;&#34;&#34;
    features = list()
    polygons = list()
    for index in group_indexes:
        features.extend(groups[index].feature_list)
        polygons.extend(groups[index].polygons)
    return Group(FeatureList(features), polygons=polygons)</code></pre>
</details>
</dd>
<dt id="py3dtilers.Common.group.Groups.set_materials"><code class="name flex">
<span>def <span class="ident">set_materials</span></span>(<span>self, materials)</span>
</code></dt>
<dd>
<div class="desc"><p>Set the materials of each group.
:param materials: an array of all the materials</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_materials(self, materials):
    &#34;&#34;&#34;
    Set the materials of each group.
    :param materials: an array of all the materials
    &#34;&#34;&#34;
    for group in self.groups:
        group.add_materials(materials)</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="py3dtilers.Common" href="index.html">py3dtilers.Common</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="py3dtilers.Common.group.Group" href="#py3dtilers.Common.group.Group">Group</a></code></h4>
<ul class="">
<li><code><a title="py3dtilers.Common.group.Group.add_materials" href="#py3dtilers.Common.group.Group.add_materials">add_materials</a></code></li>
<li><code><a title="py3dtilers.Common.group.Group.get_centroid" href="#py3dtilers.Common.group.Group.get_centroid">get_centroid</a></code></li>
<li><code><a title="py3dtilers.Common.group.Group.round_coordinates" href="#py3dtilers.Common.group.Group.round_coordinates">round_coordinates</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="py3dtilers.Common.group.Groups" href="#py3dtilers.Common.group.Groups">Groups</a></code></h4>
<ul class="">
<li><code><a title="py3dtilers.Common.group.Groups.DEFAULT_CUBE_SIZE" href="#py3dtilers.Common.group.Groups.DEFAULT_CUBE_SIZE">DEFAULT_CUBE_SIZE</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.distribute_groups_in_cubes" href="#py3dtilers.Common.group.Groups.distribute_groups_in_cubes">distribute_groups_in_cubes</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.distribute_objects_in_polygons" href="#py3dtilers.Common.group.Groups.distribute_objects_in_polygons">distribute_objects_in_polygons</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.get_groups_as_list" href="#py3dtilers.Common.group.Groups.get_groups_as_list">get_groups_as_list</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.group_array_of_feature_list" href="#py3dtilers.Common.group.Groups.group_array_of_feature_list">group_array_of_feature_list</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.group_objects_by_polygons" href="#py3dtilers.Common.group.Groups.group_objects_by_polygons">group_objects_by_polygons</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.group_objects_with_kdtree" href="#py3dtilers.Common.group.Groups.group_objects_with_kdtree">group_objects_with_kdtree</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.merge_groups_together" href="#py3dtilers.Common.group.Groups.merge_groups_together">merge_groups_together</a></code></li>
<li><code><a title="py3dtilers.Common.group.Groups.set_materials" href="#py3dtilers.Common.group.Groups.set_materials">set_materials</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>